package com.stark.druid.admin.service;

import com.alibaba.druid.stat.DruidStatService;
import com.alibaba.druid.stat.DruidStatServiceMBean;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.dtflys.forest.Forest;
import com.stark.druid.admin.boot.properties.DruidAdminProperties;
import com.stark.druid.admin.domain.Service;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;

import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@NoArgsConstructor
@AllArgsConstructor
@Slf4j
public class DruidAdminService implements DruidStatServiceMBean {
	
    private DiscoveryClient discoveryClient;
    
    private DruidAdminProperties druidAdminProperties;
    
    public String service(String url) {
    	if (url.equals("/service.json")) {
    		JSONArray services = new JSONArray();
    		getServices().forEach(service -> {
				JSONObject serviceJson = new JSONObject();
				serviceJson.put("id", service.getId());

				JSONArray instances = new JSONArray();
				service.getInstances().forEach(instance -> {
					JSONObject instanceJson = new JSONObject();
					String instanceId = getInstanceId(instance);
					String instanceIdEncoded = Base64.encodeBase64URLSafeString(instanceId.getBytes(StandardCharsets.UTF_8));
					instanceJson.put("instanceId", instanceId);
					instanceJson.put("instanceIdEncoded", instanceIdEncoded);
					instances.add(instanceJson);
				});
				serviceJson.put("instances", instances);
				services.add(serviceJson);
			});
    		return DruidStatService.returnJSONResult(DruidStatService.RESULT_CODE_SUCCESS, services);
    	}
    	
        Map<String, String> parameters = DruidStatService.getParameters(url);
        String serviceId = parameters.get("serviceId");
        String serviceInstanceId = parameters.get("instanceId");
		serviceInstanceId = new String(Base64.decodeBase64(serviceInstanceId), StandardCharsets.UTF_8);
        parameters.remove("serviceId");
        parameters.remove("instanceId");
        url = StringUtils.substringBefore(url, "?") + "?" + getQueryString(parameters);
        return delegate(serviceId, serviceInstanceId, url);
    }
    
    private List<Service> getServices() {
        return discoveryClient.getServices()
    			.stream()
	    		.filter(serviceId -> druidAdminProperties.getApplications().stream().anyMatch(application -> application.equalsIgnoreCase(serviceId)))
	    		.map(serviceId -> {
	    			Service service = new Service(serviceId);
	    			List<ServiceInstance> instances = discoveryClient.getInstances(serviceId);
	    			service.setInstances(instances);
	    			return service;
	    		})
	    		.sorted(Comparator.comparing(Service::getId))
	    		.collect(Collectors.toList());
    }
    
    private String getInstanceId(ServiceInstance instance) {
		String instanceId = instance.getInstanceId();
        if (instanceId == null) {
        	 instanceId = instance.getHost() + ":" + instance.getPort();
        }
        return instanceId;
    }
    
    private String delegate(String serviceId, String serviceInstanceId, String url) {
    	List<ServiceInstance> instances = discoveryClient.getInstances(serviceId);
    	ServiceInstance instance = instances.stream().filter(it -> serviceInstanceId.equals(getInstanceId(it))).findFirst().orElse(null);
    	if (instance == null) {
    		return DruidStatService.returnJSONResult(DruidStatService.RESULT_CODE_ERROR, "No such a service instance: serviceId=" + serviceId + ",serviceInstanceId=" + serviceInstanceId + ".");
    	}
    	url = StringUtils.defaultIfBlank(instance.getScheme(), "http") + "://" + instance.getHost() + ":" + instance.getPort() + "/druid" + url;
    	
    	String responseJson;
    	try {
			responseJson = Forest.get(url).execute(String.class);
		} catch (Exception e) {
			log.error("Delegate to service instance error: serviceId={},serviceInstanceId={},url={}",
					serviceId, serviceInstanceId, url, e);
			return DruidStatService.returnJSONResult(DruidStatService.RESULT_CODE_ERROR, "Delegate to service instance error : " + e.getMessage());
		}
    	return responseJson;
    }

	/**
	 * 根据 url 参数 map 获得查询字符串。
	 * @param queryParamMap 参数 map 。
	 */
	private String getQueryString(Map<String, String> queryParamMap) {
		if (MapUtils.isEmpty(queryParamMap)) {
			return "";
		}

        return queryParamMap.entrySet()
				.stream()
				.filter(entry -> StringUtils.isNotBlank(entry.getKey()) && StringUtils.isNotBlank(entry.getValue()))
				.map(entry -> entry.getKey() + "=" + URLEncoder.encode(entry.getValue(), StandardCharsets.UTF_8))
				.reduce("", (s1, s2) -> s1 + (s1.isEmpty() ? "" : "&") + s2);
	}
    
}
